# 在很多场景中经常会用到统计计数的需求，比如在实现 kNN 算法时统计 k 个标签值的个数，进而找出标签个数最多的标签值作为最终 kNN 算法的预测结果。Python内建的 collections 集合模块中的 Counter 类能够简洁、高效的实现统计计数。
#
# Counter 是 dict 字典的子类，Counter 拥有类似字典的 key 键和 value 值，只不过 Counter 中的键为待计数的元素，而 value 值为对应元素出现的次数 count，
# 为了方便介绍统一使用元素和 count 计数来表示。虽然 Counter 中的
# count 表示的是计数，但是 Counter 允许 count 的值为 0 或者负值。
#
# 1. 实例化 Counter 类
# 如果要使用 Counter，必须要进行实例化，在实例化的同时可以为构造函数传入参数来指定不同类型的元素来源。

from collections import Counter

# 实例化元素为空的 Counter 对象
a = Counter()

# 从可迭代对象中实例化 Counter 对象
b = Counter('chenkc')

# 从 mapping 中实例化 Counter 对象
c = Counter({'a':1, 'b':2, 'c':3})

# 从关键词参数中实例化 Counter 对象
d = Counter(a = 1, b = 2, c = 3)
# 实例化元素为空的 Counter 对象，之后可以通过为字典添加元素的方式为 Counter 对象添加元素。
from collections import Counter

# 实例化元素为空的 Counter
a = Counter()
# 为 Counter 添加元素以及对应的 count 计数
a['a'] = 1
a['b'] = 2
a['c'] = 3
print("1------->")
print(a)
Counter({'c': 3, 'b': 2, 'a': 1})
# 从 string（字符为list列表的元素）、list 和 tuple 这些可迭代对象中获取元素。
from collections import Counter

# 从可迭代对象中实例化 Counter
b = Counter("chenkc") # string
b2 = Counter(['c', 'h', 'e', 'n', 'k', 'c']) # list
b3 = Counter(('c', 'h', 'e', 'n', 'k', 'c')) # tuple
print("2------->")
print(b)
Counter({'c': 2, 'h': 1, 'e': 1, 'n': 1, 'k': 1})

print("3------->")
print(b2)
Counter({'c': 2, 'h': 1, 'e': 1, 'n': 1, 'k': 1})

print("4------->")
print(b3)
Counter({'c': 2, 'h': 1, 'e': 1, 'n': 1, 'k': 1})
# 从 mapping 中实例化 Counter 对象，mapping 类型的数据就是元素为(x, y)的列表，字典也属于 mapping 类型的数据。
from collections import Counter

# 从 mapping 中实例化 Counter 对象
c = Counter([('a', 1), ('b', 2), ('a', 3), ('c', 3)])
c2 = Counter({'a':1, 'b':2, 'a':3, 'c':3}) # 字典


print("5------->")
print(c)
Counter({('a', 1): 1, ('b', 2): 1, ('a', 3): 1, ('c', 3): 1})

print("6------->")
print(c2)
Counter({'a': 3, 'c': 3, 'b': 2})
# 虽然传入的 mapping 类型的数据是一样的，但是由于字典中的键是唯一的，因此如果字典中的键重复会保留最后一个。

dic = {'a':1, 'b':2, 'a':3, 'c':3}

print("7------->")
print(dic)
# {'a': 3, 'b': 2, 'c': 3}
# 从关键词参数中实例化 Counter 对象，关键词参数中指定的关键词必须是唯一的，但是不同于字典，如果指定的关键词重复，程序会抛出SyntaxError异常。
from collections import Counter

# 从关键词参数中实例化 Counter 对象
d = Counter(a = 1, b = 2, c = 3)
# d2 = Counter(a = 1, b = 2, a = 3, c = 3) # SyntaxError

print("8------->")
print(d)
Counter({'c': 3, 'b': 2, 'a': 1})
# 我们都知道在字典中查找不存在的键，程序会抛出 KyeError的异常，但是由于 Counter 用于统计计数，因此 Counter 不同于字典，**如果在 Counter 中查找一个不存在的元素，不会产生异常，而是会返回 0，这其实很好理解，Counter 计数将不存在元素的 count 值设置为 0 **。

from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})

print("9------->")
print(c['d']) # 查找键值为'd'对应的计数


print("10------->")
print(c)
Counter({'c': 3, 'b': 2, 'a': 1})
# c['d']表示的查找返回元素值为d的 count 计数，而如果使用c['d'] = 0则表示的是为 Counter 添加元素。

from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})
c['d'] = 4 # 为 Counter 添加元素

print("11------->")
print(c['d'])

print("12------->")
print(c)
Counter({'d': 4, 'c': 3, 'b': 2, 'a': 1})
# 2. Counter 中的方法
# 实例化 Counter 类对象之后，就可以使用 Counter 对象中的方法。由于 Counter 类继承自 dict 类，所以 Counter 类可以使用 dict 类的方法。下面分别从 Counter 所特有的方法和一些字典的常规方法来介绍。
#
# 2.1 Counter 特有的方法
# Counter 额外支持字典中没有的三个方法：elements()、most_common([m])以及subtract([iterable-or-mapping])。
#
# elements 方法
# elements()方法返回一个迭代器，可以通过 list 或者其它方法将迭代器中的元素输出，输出的结果为对应出现次数的元素。

from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})
c2 = Counter({'a':0, 'b':-1, 'c':3}) # 将出现次数设置为 0 和负值

print("13------->")
print(c.elements())


print("14------->")
print(list(c.elements()))
# ['a', 'b', 'b', 'c', 'c', 'c']


print("15------->")
print(c2.elements())


print("16------->")
print(list(c2.elements()))
# ['c', 'c', 'c']
# 在 Counter 中是允许计数为 0 或者负值的，不过通过上面代码可以看出 elements 函数没有将 0 和负值对应的元素值打印出来。

# most_common 方法
# most_common([n])是 Counter 最常用的方法，返回一个出现次数从大到小的前 n 个元素的列表。

from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})

print("17------->")
print(c.most_common()) # 默认参数
# [('c', 3), ('b', 2), ('a', 1)]

print("18------->")
print(c.most_common(2)) # n = 2
# [('c', 3), ('b', 2)]
# print(c.most_common(3)) # n = 3
# [('c', 3), ('b', 2), ('a', 1)]

print("19------->")
print(c.most_common(-1)) # n = -1
# []
# n为可选参数，通过上面的代码可以总结出：
#
# 不输入n，默认返回所有；
# 输入n小于最长长度，则返回前n个数；
# 输入n等于最长长度，则返回所有；
# 输入n = -1，则返回空；
# subtract 方法
# subtract([iterable_or_mapping])方法其实就是将两个 Counter 对象中的元素对应的计数相减。

from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3})
d = Counter({'a':1, 'b':3, 'c':2, 'd':2})
c.subtract(d)

print("20------->")
print(c)
# Counter({'c': 1, 'a': 0, 'b': -1, 'd': -2})
# 其实就是两个 Counter 中的对应的元素的计数相减。当其中某个 Counter 中对应的元素不存在的时候，默认将其计数设置为 0，这也是为什么'd'的计数为-2的原因。
#
# 2.2 Counter 支持的字典方法
# 一般常规的字典方法对 Counter 对象都是有效的，将这些字典方法作用到下面的 Counter 对象c中，并绘制到下面的表格中。

from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3, 'd':0, 'e':-1})

# 但是在 Counter 中有两个方法和字典中的使用有些区别：



from collections import Counter

c = Counter({'a':1, 'b':2, 'c':3, 'd':0, 'e':-1})

c.update({'a':2, 'd':2, 'e':1})

print("21------->")
print(c)
Counter({'a': 3, 'c': 3, 'b': 2, 'd': 2, 'e': 0})
# 对于 Counter 中的update函数简单来说，就是增加对应元素的计数。

# 2.3 集合运算符
# 这里直接贴出集合运算符的代码示例。

c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c + d                           # add two counters together:  c[x] + d[x]
Counter({'a': 4, 'b': 3})
c - d                           # subtract (keeping only positive counts)
Counter({'a': 2})
c & d                           # intersection:  min(c[x], d[x])
Counter({'a': 1, 'b': 1})
c | d                           # union:  max(c[x], d[x])
